<?php
declare(strict_types=1);

namespace think;

use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress;

//use BitWasp\Bitcoin\Bitcoin;
use BitWasp\Bitcoin\Crypto\Random\Random;
use BitWasp\Bitcoin\Exceptions\RandomBytesFailure;
use BitWasp\Bitcoin\Key\Factory\HierarchicalKeyFactory;
use BitWasp\Bitcoin\Mnemonic\Bip39\Bip39Mnemonic;
use BitWasp\Bitcoin\Mnemonic\Bip39\Bip39SeedGenerator;
use BitWasp\Bitcoin\Mnemonic\MnemonicFactory;
use BitWasp\Bitcoin\Network\NetworkFactory;
use Exception;
use IEXBase\TronAPI\Exception\TronException;
use IEXBase\TronAPI\Provider\HttpProvider;
use kornrunner\Keccak;
use Phactor\Key;
use think\facade\Cache;
use think\support\Address;
use think\support\Base58;
use think\support\Crypto;
use think\support\Hash;
use Elliptic\EC;
use Web3p\EthereumUtil\Util;

/**
 * | Notes：Tron
 * +----------------------------------------------------------------------
 * | PHP Version 7.2+
 * +----------------------------------------------------------------------
 * | Copyright (c) 2011-2020 https://www.xxq.com.cn, All rights reserved.
 * +----------------------------------------------------------------------
 * | Author: 阶级娃儿 <devloper@zhujinkui.com>
 * +----------------------------------------------------------------------
 * | Date: 2020/11/18 17:47
 * +----------------------------------------------------------------------
 */
class Tron
{
    protected \IEXBase\TronAPI\Tron $tron;

    protected HttpProvider $full_node;
    protected HttpProvider $solidity_node;
    protected HttpProvider $event_Server;

    protected string $private_key;

    // 开发者地址
    protected string $dev_address;

    /**
     * 节点
     * https://api.trongrid.io 正式节点
     * https://api.shasta.trongrid.io 测试节点
     *
     * @var string
     */
    protected static string $host = "https://api.trongrid.io";

    public function __construct(string $dev_address = '', string $private_key = '')
    {
        try {
            // 节点
            $this->full_node = new HttpProvider(self::$host);
            // 合约节点
            $this->solidity_node = new HttpProvider(self::$host);
            // 事件节点
            $this->event_Server = new HttpProvider(self::$host);

            // 地址
            $this->dev_address = $dev_address;
            // 私钥
            $this->private_key = $private_key;

            // 公链对象
            $this->tron = new \IEXBase\TronAPI\Tron($this->full_node, $this->solidity_node, $this->event_Server, null, null, $this->private_key);
        } catch (TronException $e) {
            exit($e->getMessage());
        }
    }

    /**
     * 获取当前区块
     *
     * @return array
     * @throws TronException
     */
    public function getCurrentBlock()
    {
        return $this->tron->getCurrentBlock();
    }

    /**
     * 通过高度查询区块内容
     *
     * @param int $num
     *
     * @return array
     * @throws TronException
     */
    public function getBlockByNumber(int $num)
    {
        return $this->tron->getBlockByNumber($num);
    }

    /**
     * 生成助记词
     *
     * @return string
     * @throws RandomBytesFailure
     */
    protected function generateMnemonic()
    {
        $random = new Random();
        // 生成随机数(initial entropy)
        $entropy = $random->bytes(Bip39Mnemonic::MIN_ENTROPY_BYTE_LEN);
        $bip39   = MnemonicFactory::bip39();
        return $bip39->entropyToMnemonic($entropy);
    }

    /**
     * 根据私钥获取地址
     *
     * @param string $private_key
     *
     * @return string
     * @throws Exception
     */
    public function getAddressByPrivateKey(string $private_key)
    {
        //https://github.com/simplito/elliptic-php
        $ec          = new EC('secp256k1');
        $key_pair    = $ec->keyFromPrivate($private_key);
        $public_key  = $key_pair->getPublic()->encode('hex');//拿到这个公钥推算地址
        $pub_key_bin = hex2bin($public_key);
        $address_hex = $this->getAddressHex($pub_key_bin);
        $address_bin = hex2bin($address_hex);
        return $this->getBase58CheckAddress($address_bin);
    }

    /**
     * 创建助记词并生成私钥(WIF格式，可用于导入其他钱包)、地址
     *
     * @param string $symbol
     *
     * @return array
     * @throws Exception
     */
    public function createAddress(string $symbol = '')
    {
        // Bip39
//        $math    = Bitcoin::getMath();
//        $network = Bitcoin::getNetwork();

        // 生成助记词
        $mnemonic = $this->generateMnemonic();

        // 助记词产生主私钥和主公钥
        $send_generator = new Bip39SeedGenerator();

        // 通过助记词生成种子，传入可选加密串'hello'
        $seed = $send_generator->getSeed($mnemonic, 'hello');

        $hd_factory = new HierarchicalKeyFactory();
        $master     = $hd_factory->fromEntropy($seed);

        switch ($symbol) {
            case "BTC";
                // 比特比
                $hardened   = $master->derivePath("49'/0'/0'/0/0");
                $public_key = $hardened->getPublicKey()->getHex();
                $prive_key  = $hardened->getPrivateKey()->getHex();

                $address = new PayToPubKeyHashAddress($hardened->getPublicKey()->getPubKeyHash());

                $result = [
                    "mnemonic"   => $mnemonic,
                    "public_key" => $public_key,
                    "prive_key"  => $prive_key,
                    "address"    => $address->getAddress(),
                ];
                break;
            case "ETH";
                // 以太坊
                $util       = new Util();
                $hardened   = $master->derivePath("44'/0'/0'/0/0");
                $public_key = $hardened->getPublicKey()->getHex();
                $prive_key  = $hardened->getPrivateKey()->getHex();
                $address    = $util->publicKeyToAddress($util->privateKeyToPublicKey($hardened->getPrivateKey()->getHex()));
                $result     = [
                    "mnemonic"   => $mnemonic,
                    "public_key" => $public_key,
                    "prive_key"  => $prive_key,
                    "address"    => $address,
                ];
                break;
            case "LTC";
                // 设置莱特币网络
                $network = NetworkFactory::litecoin();

                $hardened   = $master->derivePath("44'/0'/0'/0/0");
                $public_key = $hardened->getPublicKey()->getHex();
                $prive_key  = $hardened->getPrivateKey()->getHex();
                $address    = new PayToPubKeyHashAddress($hardened->getPublicKey()->getPubKeyHash());
                $result     = [
                    "mnemonic"   => $mnemonic,
                    "public_key" => $public_key,
                    "prive_key"  => $prive_key,
                    "address"    => $address->getAddress($network),
                ];
                break;
            default:
                // 比特比
                $result = $this->generateAddress();
                break;
        }

        return $result;
    }

    /**
     * 本地生成地址
     *
     * @return array|false
     * @throws TronException
     * @throws Exception
     */
    public function generateAddress()
    {
        $attempts      = 0;
        $valid_address = false;
        $address       = [];

        do {
            if ($attempts++ === 100) {
                //这里应该是返回系统错误
                return false;
            }

            $key_pair = $this->genKeyPair();

            //带有0x的私钥。
            $private_key_hex = $key_pair['private_key_hex'];
            $public_key_hex  = $key_pair['public_key'];

            //We cant use hex2bin unless the string length is even.
            if (strlen($public_key_hex) % 2 !== 0) {
                continue;
            }

            $pub_key_bin = hex2bin($public_key_hex);

            $address_hex = $this->getAddressHex($pub_key_bin);

            $address_hex_bin = hex2bin($address_hex);

            $address_base58 = $this->getBase58CheckAddress($address_hex_bin);

            //不带0x的私钥
            $private_key = substr($private_key_hex, 2);

            $address = new Address($address_base58, $private_key, $address_hex);

            $valid = $this->validateAddress($address_base58);

            if (isset($result['address']) && !empty($address) && isset($valid['result']) && $valid['result'] == true) {
                $valid_address = true;
            } else {
                $valid_address = false;
            }
        } while ($valid_address);

        return (array)$address;
    }

    /**
     * 地址转HEX
     *
     * @param string $pub_key_bin
     *
     * @return string
     * @throws Exception
     */
    public function getAddressHex(string $pub_key_bin): string
    {
        if (strlen($pub_key_bin) == 65) {
            $pub_key_bin = substr($pub_key_bin, 1);
        }

        $hash = Keccak::hash($pub_key_bin, 256);

        return Address::ADDRESS_PREFIX . substr($hash, 24);
    }

    /**
     * 生成KEY
     *
     * @return array
     */
    protected function genKeyPair(): array
    {
        $key = new Key();
        return $key->GenerateKeypair();
    }

    /**
     * 查询链上数据
     *
     * @param int $num
     *
     * @return bool|string
     */
    public function getBlockByNum(int $num)
    {
        $data = [
            "num" => $num
        ];

        return http_request(self::$host . "/wallet/getblockbynum", "POST", json_encode($data));
    }

    /**
     * 获取BASE58
     *
     * @param string $address_bin
     *
     * @return string
     */
    protected function getBase58CheckAddress(string $address_bin): string
    {
        $hash0    = Hash::SHA256($address_bin);
        $hash1    = Hash::SHA256($hash0);
        $checksum = substr($hash1, 0, 4);
        $checksum = $address_bin . $checksum;

        return Base58::encode(Crypto::bin2bc($checksum));
    }

    /**
     * 获取地址转HEX
     *
     * @param string $address
     *
     * @return string
     */
    public function getAddressToHex(string $address)
    {
        return $this->tron->toHex($address);
    }

    /**
     * 验证地址
     *
     * @param $address
     *
     * @return array
     * @throws TronException
     */
    public function validateAddress($address)
    {
        return $this->tron->validateAddress($address);
    }

    /**
     * 获取合约
     *
     * @param string $contract_address
     *
     * @return mixed
     */
    public function getContract(string $contract_address = '')
    {
        $base58_address = $this->tron->toHex($contract_address);

        $data = [
            "value" => $base58_address,
        ];

        $contract_address_data = Cache::get($contract_address);

        if (!$contract_address_data && empty($contract_address_cache)) {
            $contract_address_data = http_request(self::$host . "/wallet/getcontract", "POST", $data, true);

            if (is_json($contract_address_data)) {
                Cache::set($contract_address, $contract_address_data);
            }
        }

        return $contract_address_data;
    }

    /**
     * 获取代币余额
     *
     * @param string $contract_address
     * @param string $address
     *
     * @return float|int
     * @throws Exception
     */
    public function getTokenBalance(string $contract_address, string $address)
    {
        $request_data = curl_request_data("https://apilist.tronscan.io/api/account?address=" . $address . "", "GET");

        if (!is_json($request_data)) throw new Exception("网络异常,请重新尝试");

        $tronscan_data = json_decode($request_data, true);

        if (!isset($tronscan_data["trc20token_balances"]) || empty($tronscan_data["trc20token_balances"])) throw new Exception("网络异常,请重新尝试");

        foreach ($tronscan_data['trc20token_balances'] as $key => $value) {
            $tronscan_address[$value['tokenId']] = $value;
        }

        return isset($tronscan_address[$contract_address]['balance']) ? $this->fromTronExt($tronscan_address[$contract_address]['balance']) : 0;
    }

    /**
     * 获取代币
     *
     * @param string $contract_address
     * @param string $address
     *
     * @return array|float
     * @throws TronException
     */
    public function getContractTokenBalance(string $contract_address, string $address)
    {
        $function   = "balanceOf";
        $to_address = $this->tron->address2HexString($address);
        //这一步返回的是[object] (phpseclib\\Math\\BigInteger: 300000000)
        return $balance_obj = $this->getTriggerContract($contract_address, $function, [$to_address], $address);
        //trx格式的余额
        $balance_value = $balance_obj['balance']->toString();
        //浮点型余额
        return $this->fromTronExt($balance_value);
    }

    /**
     * 代币转账
     *
     * @param string $contract_address
     * @param string $to_address
     * @param string $amount
     *
     * @return array
     * @throws TronException
     */
    public function transferToToken(string $contract_address, string $to_address, string $amount)
    {
        $function          = "transfer";
        $to_address_string = $this->tron->address2HexString($to_address);
        $trx_amount        = $this->tron->toTron($amount);
        $params            = [$to_address_string, $trx_amount];
        //1.创建交易
        $result = $this->getTriggerContract($contract_address, $function, $params, $this->dev_address, 1);

        //2.签名
        $sign_data = $this->tron->signTransaction($result);
        //3.广播
        return $this->tron->sendRawTransaction($sign_data);
    }

    /**
     * 调用智能合约，返回 TransactionExtention, 需要签名后广播.
     *
     * @param string $contract_address
     * @param string $function
     * @param array  $params
     * @param string $owner_address
     * @param int    $type
     *
     * @return mixed
     * @throws TronException
     */
    protected function getTriggerContract(string $contract_address, string $function, array $params, string $owner_address, int $type = 0)
    {
        // 获取API
//        return $json_abi = $this->getContract($contract_address);
//
//        $json_abi = '[{"entrys":[{"outputs":[{"type":"string"}],"constant":true,"name":"name","stateMutability":"View","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"name":"approve","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"uint256"}],"constant":true,"name":"totalSupply","stateMutability":"View","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"sender","type":"address"},{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"transferFrom","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"uint8"}],"constant":true,"name":"decimals","stateMutability":"View","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"uint256"}],"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"balanceOf","stateMutability":"View","type":"Function"},{"outputs":[{"type":"address"}],"constant":true,"name":"dev","stateMutability":"View","type":"Function"},{"outputs":[{"type":"string"}],"constant":true,"name":"symbol","stateMutability":"View","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","stateMutability":"Nonpayable","type":"Function"},{"inputs":[{"name":"_dev","type":"address"}],"name":"setDev","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"uint256"}],"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","stateMutability":"View","type":"Function"},{"stateMutability":"Nonpayable","type":"Constructor"},{"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"Transfer","type":"Event"},{"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"Approval","type":"Event"}]}]';
//        $abi_data = json_decode($json_abi, true);
//        $abi      = $abi_data['abi']['entrys'];
        $abi      = json_decode('[{"outputs":[{"type":"string"}],"constant":true,"name":"name","stateMutability":"View","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"name":"approve","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"uint256"}],"constant":true,"name":"totalSupply","stateMutability":"View","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"sender","type":"address"},{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"transferFrom","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"uint8"}],"constant":true,"name":"decimals","stateMutability":"View","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"uint256"}],"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"balanceOf","stateMutability":"View","type":"Function"},{"outputs":[{"type":"address"}],"constant":true,"name":"dev","stateMutability":"View","type":"Function"},{"outputs":[{"type":"string"}],"constant":true,"name":"symbol","stateMutability":"View","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"bool"}],"inputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","stateMutability":"Nonpayable","type":"Function"},{"inputs":[{"name":"_dev","type":"address"}],"name":"setDev","stateMutability":"Nonpayable","type":"Function"},{"outputs":[{"type":"uint256"}],"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","stateMutability":"View","type":"Function"},{"stateMutability":"Nonpayable","type":"Constructor"},{"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"Transfer","type":"Event"},{"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"Approval","type":"Event"}]', true);

        // 合约地址, Base58check 格式或 HEX 格式
        $contract_address_hex = $this->tron->toHex($contract_address);

        // 发起合约调用的账户地址, Base58check 格式或 HEX 格式
        $owner_address_hex = $this->tron->toHex($owner_address);

        if ($type) {
            $result = $this->tron->getTransactionBuilder()->triggerSmartContract($abi, $contract_address_hex, $function, $params, 100000000, $owner_address_hex);
        } else {
            $result = $this->tron->getTransactionBuilder()->triggerConstantContract($abi, $contract_address_hex, $function, $params, $owner_address_hex);
        }

        return $result;
    }

    /**
     * 获取TRX余额
     *
     * @param string $address
     *
     * @return float
     * @throws TronException
     */
    public function getBalance(string $address)
    {
        return $this->tron->getBalance($address, true);
    }

    /**
     * TRX转账
     *
     * @param string $address
     * @param float  $money
     *
     * @param string $message
     *
     * @return array
     * @throws TronException
     */
    public function sendTransaction(string $address, float $money, string $message = '')
    {
        return $this->tron->sendTransaction($address, $money, $message, $this->dev_address);
    }

    /**
     * 查询交易流水
     *
     * @param string $txid
     *
     * @return array
     * @throws TronException
     */
    public function getTransaction(string $txid)
    {
        return $this->tron->getTransaction($txid);
    }

    /**
     * 查询交易流水金额
     *
     * @param string $txid
     *
     * @return array
     * @throws TronException
     */
    public function getTransactionInfo(string $txid)
    {
        return $this->tron->getTransactionInfo($txid);
    }

    /**
     * 地址转HEX格式
     *
     * @param string $address
     *
     * @return string
     */
    public function addressToHex(string $address)
    {
        return $this->tron->toHex($address);
    }

    /**
     * HEX转地址
     *
     * @param string $address
     *
     * @return string
     */
    public function hexToAddress(string $address)
    {
        return $this->tron->fromHex($address);
    }

    /**
     * 可转精度的fromTron
     *
     * @param     $amount
     *
     * @return float
     */
    public function fromTronExt($amount): float
    {
        return $this->tron->fromTron($amount);
    }

    /**
     * 可转精度的toTron
     *
     * @param $double
     *
     * @return int
     */
    public function toTronExt($double): int
    {
        return $this->tron->toTron($double);
    }
}